﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Net;
using System.CodeDom;
using System.CodeDom.Compiler;

namespace WebPALTT.Lib
{
    /*
     * Class: Scenario
     * 
     * This class stores information about the scenario to be run.
     * The class also has information about the conditions used to run the scenario,
     * and a few runtime objects related to the scenario.
     * 
     * + The Scenario object is the one passed in to the any custom scripts
     *   and is the starting point of accessing information about the scenario being run.
     * + The class is Serializable.
     */
    [Serializable()]
    public class Scenario : ISerializable
    {
        public string Name;
        public string Description;
        protected ActionItemCollection mActions;
        protected RunConditions mRunConditions;
        protected TimeSpan mRunDuration;
        protected List<Cookie> mCookies = new List<Cookie> { }; // doesn't get serialized - only used for runtime.

        /*
         * Name: Properties
         * 
         * /@link[ActionItemCollection.cs.htm, ActionItemCollection]/ Actions:
         * 
         * Gets the collection of actions assigned to the scenario.
         * 
         * /@link[RunConditions.cs.htm, RunConditions]/ RunConditions:
         * 
         * Gets or sets the conditions used to run the scenario.
         * 
         * /TimeSpan/ RunDuration:
         * 
         * Gets or sets the total duration of running the scenario. Only used in results.
         * 
         * /{{List<Cookie>}}/ Cookies:
         * 
         * Gets the collection of cookies for this scenario. Only used at runtime. 
         * The cookies are not serialized if the object is save as a result or project.
         */
        /// <summary>
        /// Gets the collection of actions assigned to the scenario.
        /// </summary>
        public ActionItemCollection Actions { get { return mActions; } }
        /// <summary>
        /// Gets or sets the conditions used to run the scenario.
        /// </summary>
        public RunConditions RunConditions { get { return mRunConditions; } set { mRunConditions = value; } }
        /// <summary>
        /// Gets or sets the total duration of running the scenario. Only used in results.
        /// </summary>
        public TimeSpan RunDuration { get { return mRunDuration; } set { mRunDuration = value; } }
        /// <summary>
        /// Gets the collection of cookies for this scenario. Only used at runtime. 
        /// The cookies are not serialized if the object is save as a result or project.
        /// </summary>
        public List<Cookie> Cookies { get { return mCookies; } }

        public Scenario() : this("", "") { }

        public Scenario(string name, string desc)
        {
            this.Name = name;
            this.Description = desc;
            this.mActions = new ActionItemCollection();
            this.mRunConditions = null;
        }

        #region Serialization

        public Scenario(SerializationInfo info, StreamingContext ctxt)
        {
            Name = (string)info.GetValue("Name", typeof(string));
            Description = (string)info.GetValue("Description", typeof(string));
            mActions = (ActionItemCollection)info.GetValue("Actions", typeof(ActionItemCollection));
            mRunConditions = (RunConditions)info.GetValue("RunConditions", typeof(RunConditions));
            mRunDuration = (TimeSpan)info.GetValue("RunDuration", typeof(TimeSpan));
        }

        public void GetObjectData(SerializationInfo info, StreamingContext ctxt)
        {
            info.AddValue("Name", Name);
            info.AddValue("Description", Description);
            info.AddValue("Actions", mActions);
            info.AddValue("RunConditions", mRunConditions);
            info.AddValue("RunDuration", mRunDuration);
        }

        #endregion

        public string GetScriptCode(string classname)
        {
            List<string> usingStatements = new List<string> { };
            string code = "namespace Script { public class " + classname + " {\r\n";
            ActionItem[] actions = mActions.GetScriptActions();
            foreach (ActionItem action in actions)
            {
                if (action.ActionType == ActionType.Script)
                {
                    ActionItem_Script scriptAction = (ActionItem_Script)action;
                    foreach (string state in scriptAction.UsingStatements)
                        if (!usingStatements.Contains(state))
                            usingStatements.Add(state);
                    code += "public string @" + scriptAction.MethodName + " (Scenario _Scenario) {\r\n" + 
                        scriptAction.Code + "\r\n}\r\n";
                }
                if (action.ActionType == ActionType.Variable)
                {
                    ActionItem_Variable variableAction = (ActionItem_Variable)action;
                    code += "private " + (variableAction.IsStatic ? "static " : "") + variableAction.Type + " @" + variableAction.Name +
                        (String.IsNullOrEmpty(variableAction.Value) ? ";" : " = " + variableAction.Value + ";") + "\r\n";
                }                
                if (action.ActionType == ActionType.If)
                {
                    ActionItem_If ifAction = (ActionItem_If)action;
                    code += "public bool @" + action.Name + "() {return " + (String.IsNullOrWhiteSpace(ifAction.Condition) ? "false" : ifAction.Condition) + ";}\r\n";
                }
            }

            code += "}}";

            string codetop = "";
            foreach (string str in usingStatements)
                codetop += "using " + str + ";\r\n";

            return codetop + "\r\n" + code;
        }

        public string[] GetCompileParameters()
        {
            List<string> parameters = new List<string> { };

            foreach (ActionItem action in mActions)
            {
                if (action.ActionType == ActionType.Script)
                {
                    ActionItem_Script scriptAction = (ActionItem_Script)action;
                    foreach (string par in scriptAction.CompileParameters)
                        if (!parameters.Contains(par))
                            parameters.Add(par);
                }
            }
            return parameters.ToArray();
        }

        public string[] Validate()
        {
            List<string> errors = new List<string> { };
            List<string> assemblyNames = new List<string> { "System.dll", "mscorlib.dll", "System.Core.dll", "WebPALTT.Lib.dll" };
            string code;
            foreach (string str in this.GetCompileParameters())
                    if (!assemblyNames.Contains(str))
                        assemblyNames.Add(str);
            code = this.GetScriptCode("Scenario_Test");            

            CodeDomProvider provider = new Microsoft.CSharp.CSharpCodeProvider();
            CompilerParameters mCompileParams = new CompilerParameters(assemblyNames.ToArray());
            mCompileParams.GenerateInMemory = true;
            mCompileParams.CompilerOptions = "/target:library /optimize";

            CompilerResults results = provider.CompileAssemblyFromSource(mCompileParams, new string[] {code});
            if (results.Errors.HasErrors)
            {
                foreach (CompilerError err in results.Errors)
                    errors.Add("Compile Error : " + err.ErrorText);
            }

            for (int i = 0; i < mActions.Count; i++)
            {
                for (int j = i + 1; j < mActions.Count; j++)
                    if (mActions[i].Name.ToLower().Equals(mActions[j].Name.ToLower()))
                        errors.Add("Error: Duplicate name exists [" + mActions[i].Name + "].");
                errors.AddRange(mActions[i].Validate());
            }
            return errors.ToArray();
        }

        /*
         * Method: HasMessages
         * 
         * Returns a boolean indicating if the actions in the scenario have any warings or errors.
         * 
         * Parameters:
         * 
         * warnings - if true will search for warnings (default is true)
         * errors - if true will search for errors (default is true)
         */
        public bool HasMessages(bool warnings = true, bool errors = true)
        {
            foreach (ActionItem action in mActions)
            {
                if(action.Results != null)
                {
                    if (action.Results.HasErrors && errors)
                        return true;
                    if (action.Results.HasWarnings && warnings)
                        return true;
                }
            }
            return false;
        }
        /*
         * Method: GetMessages
         * 
         * Returns an array of warinings and errors from the scenario.
         * 
         * Parameters:
         * 
         * warnings - if true will add warnings (default is true)
         * errors - if true will add errors (default is true)
         */
        public string[] GetMessages(bool warnings = true, bool errors = true)
        {
            List<string> messages = new List<string> { };
            foreach (ActionItem action in mActions)
            {
                if (action.Results != null)
                {
                    if (errors)
                        messages.AddRange(action.Results.Errors);
                    if (warnings)
                        messages.AddRange(action.Results.Warnings);
                }
            }
            return messages.ToArray();
        }
        /*
         * Method: CountMessages
         * 
         * Returns an integer counting the number of warinings and errors from the scenario.
         * 
         * Parameters:
         * 
         * warnings - if true will count warnings (default is true)
         * errors - if true will count errors (default is true)
         */
        public int GetMessageCount(bool warnings = true, bool errors = true)
        {
            int count = 0;
            foreach (ActionItem action in mActions)
            {
                if (action.Results != null)
                {
                    if (errors)
                        count += action.Results.Errors.Count;
                    if (warnings)
                        count += action.Results.Warnings.Count;
                }
            }
            return count;
        }

        /*
         * Method: GetCookie
         * 
         * Returns the first cookie with the same name or null if the name doesn't match.
         */
        /// <summary>
        /// Returns the first cookie with the same name or null if the name doesn't match.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public Cookie GetCookie(string name)
        {
            for (int i = 0; i < mCookies.Count; i++)
                if (mCookies[i].Name.ToUpper().Equals(name.ToUpper()))
                    return mCookies[i];
            return null;
        }

        /*
         * Method: GetCookieIndex
         * 
         * Returns the index of the first cookie with the same name or -1 if the name doesn't match.
         */
        /// <summary>
        /// Returns the index of the first cookie with the same name or -1 if the name doesn't match.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public int GetCookieIndex(string name)
        {
            for (int i = 0; i < mCookies.Count; i++)
                if (mCookies[i].Name.ToUpper().Equals(name.ToUpper()))
                    return i;
            return -1;
        }

        /*
         * Method: SetCookie
         * 
         * Updates an existing cookie with the same name or creates a new cookie if the name doesn't match.
         */
        /// <summary>
        /// Updates an existing cookie with the same name or creates a new cookie if the name doesn't match.
        /// </summary>
        /// <param name="cookie"></param>
        public void SetCookie(Cookie cookie)
        {
            if (cookie == null)
                throw new ArgumentNullException("cookie is null");
            int i = GetCookieIndex(cookie.Name);
            if (i == -1)
                mCookies.Add(cookie);
            else
                mCookies[i] = cookie;
        }
    }

    [Serializable()]
    public class RunConditions
    {
        public int VirtualUsers;
        public RunMode RunMode;

        public int StartingUsers;
        public int RampUpUsers;
        public int RampUpTimeSpan;
        public bool StopOnUserCount;
        public bool StopOnMaxTime;
        public int MaxTime;

        public RunConditions()
        {
            VirtualUsers = 1;
            RunMode = WebPALTT.Lib.RunMode.Concurrent;
            StartingUsers = 1;
            RampUpUsers = 0;
            RampUpTimeSpan = 0;
            StopOnUserCount = true;
            StopOnMaxTime = false;
            MaxTime = 0;
        }

        public RunConditions(SerializationInfo info, StreamingContext ctxt)
        {
            VirtualUsers = (int)info.GetValue("VitualUsers", typeof(int));
            RunMode = (RunMode)info.GetValue("RunMode", typeof(RunMode));

            StartingUsers = (int)info.GetValue("StartingUsers", typeof(int));
            RampUpUsers = (int)info.GetValue("RampUpUsers", typeof(int));
            RampUpTimeSpan = (int)info.GetValue("RampUpTimeSpan", typeof(int));
            StopOnUserCount = (bool)info.GetValue("StopOnUserCount", typeof(bool));
            StopOnMaxTime = (bool)info.GetValue("StopOnMaxTime", typeof(bool));
            MaxTime = (int)info.GetValue("MaxTime", typeof(int));
        }

        public void GetObjectData(SerializationInfo info, StreamingContext ctxt)
        {
            info.AddValue("VitualUsers", VirtualUsers);
            info.AddValue("RunMode", RunMode);

            info.AddValue("StartingUsers", StartingUsers);
            info.AddValue("RampUpUsers", RampUpUsers);
            info.AddValue("RampUpTimeSpan", RampUpTimeSpan);
            info.AddValue("StopOnUserCount", StopOnUserCount);
            info.AddValue("StopOnMaxTime", StopOnMaxTime);
            info.AddValue("MaxTime", MaxTime);
        }
    }
}
